package in.co.codedoc.sql;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class QueryStatement
	extends SQLStatement
{
	public QueryStatement()
	{
		where = new WhereClause(this);
	}
		
	public boolean FetchNextRow() 
		throws SQLException
	{
		ResultSet rs = GetResultSet();
	
		if(rs.next())
		{
			if(Logger.logger.isDebugEnabled())
			{
				PushDebugStringBuffer();
				GetDebugStringBuffer().append("Record [");
			}
			
			int index = 1;
			for(TableColumn tc:columns)
			{
				columnMapByColumn.get(tc).ReadColumn(index,rs);
				index++;
			}
			
			if(Logger.logger.isDebugEnabled())
			{
				GetDebugStringBuffer().append("]");
				Logger.logger.debug(PopDebugStringBuffer().toString());
			}
			
			return true;
		}
		else
		{
			if(Logger.logger.isDebugEnabled())
			{
				Logger.logger.debug("End of records");
			}
			return false;
		}
	}
	
	@Override
	public PrintWriter Generate(PrintWriter out) throws IOException
	{
		out.write("SELECT ");
		
		boolean firstColumn = true;
		
		Set<TableColumn> unAliasedColumns = new HashSet<TableColumn>();
		
		for(TableColumn tc:columns)
		{
			if(!firstColumn)
			{
				out.write(',');
			}

			if(!tc.GetTable().HasAlias())
			{
				unAliasedColumns.add(tc);
			}

			out.write(tc.GetQualifiedName());
			
			firstColumn = false;
		}
		
		out.write(" FROM ");
		boolean firstTable = true;
		Set<Table> tables = GatherTables(columns);
		tables.addAll(Where().GetTables());
		
		if(tables.size() > 1 && !unAliasedColumns.isEmpty())
		{
			throw new RuntimeException
			(
				"When using a join, the Query columns must specify an alias." + 
				" Columns without alias:" + unAliasedColumns + "." +
				" The most common mistake is that an alias is not passed to the static Find method when the where-clause specifies joins." + 
				" If you have a hand written QueryStatement, make sure all your columns know the table alias they come from (using AliasedTableColumn)."
			);
		}
		
		Set<Table> unAliasedTables = new HashSet<Table>();
		for(Table t:tables)
		{
			if(!firstTable)
			{
				out.write(',');
			}
			
			if(!t.HasAlias())
			{
				unAliasedTables.add(t);
			}
			
			out.write(t.GetTableName());
			if(t.HasAlias())
			{
				out.write(' ');
				out.write(t.GetAlias());
			}
			firstTable = false;
		}
		
		if(tables.size() > 1 && !unAliasedTables.isEmpty())
		{
			throw new RuntimeException
			(
				"When using a join, every table must have an alias." + 
				" Unaliased tables:" + unAliasedTables
			);
		}
		
		if(!where.IsEmpty())
		{
			out.write(" WHERE ");
			where.Generate(out);
		}

		if(!groupings.isEmpty())
		{
			out.write(" GROUP BY ");
			firstColumn = true;
			for(TableColumn tc:groupings)
			{
				if(!firstColumn)
				{
					out.write(',');
				}

				out.write(tc.GetQualifiedName());
				
				firstColumn = false;
			}
		}
		
		if(ordering != null)
		{
			ordering.Generate(out);
		}
		
		if(limit != null)
		{
			limit.Generate(out);
		}
		
		return out;
	}

	@Override
	public void GetParameterValues(LinkedList<DBInputColumnMap> values)
	{
		where.GetParameterValues(values);
	}

	public QueryStatement Select(TableColumn tc)
	{
		DBOutputColumnMap map = tc.GetDBOutputColumnMap();
		
		return Select(tc,map);
	}
	
	public QueryStatement Select(TableColumn tc,DBOutputColumnMap columnMap)
	{	
		Invalidate();
		
		columnMapByColumn.put(tc, columnMap);
		
		columns.add(tc);
		
		return this;
	}
	
	public DBOutputColumnMap GetColumnMap(TableColumn tc)
	{
		return columnMapByColumn.get(tc);
	}

	public WhereClause Where()
	{
		return where;
	}
	
	public void SetOrdering(OrderingClause ordering)
	{
		this.ordering = ordering;
		this.ordering.SetOwner(this);
	}
	
	public void SetLimit(LimitClause limit)
	{
		this.limit = limit;
	}
	
	public OrderingClause OrderBy()
	{
		if(ordering == null)
		{
			ordering = new OrderingClause(this);
		}
			 
		return ordering;
	}

	public QueryStatement GroupBy(TableColumn tc)
	{
		groupings.add(tc);
		return this;
	}
	
	private LinkedList<TableColumn> columns = new LinkedList<TableColumn>();
	private HashMap<TableColumn, DBOutputColumnMap> columnMapByColumn = new HashMap<TableColumn, DBOutputColumnMap>();

	private LinkedList<TableColumn> groupings = new LinkedList<TableColumn>();
	
	private WhereClause where;
	private OrderingClause ordering;
	private LimitClause limit;
}
